/***************************************************************************
 *   Copyright (C) 2007 by Grzegorz Latarowski, Maciej Gajewski            *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <QDataStream>

#include "gnettypes.h"
#include "gexception.h"

// ===========================================================================
//                        GSerializableItem
// ===========================================================================


// ----------------------------- write() -------------------------------------
void GSerializableItem::write( QIODevice * device ) const
{
        // check out for a proper pointer
    if( !device ) {
        return;
    }
    
        // create stream to write to
    QDataStream out( device );
    out.setVersion( QDATASTREAM_VERSION  );
    
    write( out );
}
// ------------------------------------ readBuffer ---------------------------------------
/// Reads QByteArray from device, using QdataStream format. Ensures device has all needed bytes
/// Available for read.
///@param	pDevice	i/o device to read from
///@param	buffer	output - target buffer
///@param	timeout read timeout.
///throws exception on timeout
void GSerializableItem::readBuffer( QIODevice* pDevice, QByteArray& buffer /* OUT */, int timeout )
{
	Q_ASSERT( pDevice );
	
	// wait for size
	while( (size_t)pDevice->bytesAvailable() < sizeof( quint32 ) )
	{
		if ( ! pDevice->waitForReadyRead( timeout ) )
		{
			throw GTimeoutError("Timeout reading byte array");
		}
	}
	// read size
	quint32 size = 0xFFffFFff;
	QDataStream( pDevice ) >> size;
	
	// is array null?
	if ( size == 0xFFffFFff )
	{
		buffer = QByteArray(); // set to null buffer;
		return; // success anyway
	}
	
	// read buffer
	buffer.resize( size );
	
	// read
	qint64 toRead = size;
	char* pData = buffer.data();
	while( toRead > 0 )
	{
		qint64 readed	= pDevice->read( pData, toRead );
		
		// chek for error
		if ( readed < 0 )
		{
			throw GSysError( "Error writing byte array");
		}
		toRead		-= readed;
		pData		+= readed;
		
		// all data red?
		if ( toRead == 0 )
		{
			break;
		}
		
		// wait for more
		if ( ! pDevice->waitForReadyRead( timeout ) )
		{
			throw GTimeoutError("Timeout reading byte array data");
		}
	}
}

// ------------------------------------ writeBuffer ---------------------------------------
/// writes QByteArray to asynchronous device, using QDataStream format. Ensures device has all needed bytes
/// send
///@param	pDevice	i/o device to write to
///@param	buffer	data buffer
///@param	timeout write timeout.
///throws exception on timeout
void GSerializableItem::writeBuffer( QIODevice* pDevice, const QByteArray& buffer, int timeout )
{
	Q_ASSERT( pDevice );
	
	// write size
	quint32 size = 0xFFffFFff;
	if ( ! buffer.isNull() )
	{
		size = buffer.size();
	}
	QDataStream( pDevice ) << size;
	
	// make sure size is written
	if ( ! pDevice->waitForBytesWritten( timeout ) )
	{
		throw GTimeoutError("Timeout writing byte array");
	}
	
	// read
	qint64 toWrite = buffer.size();
	const char* pData = buffer.data();
	while( toWrite > 0 )
	{
		qint64 written	= pDevice->write( pData, toWrite );
		// chech for error
		if ( written < 0 )
		{
			throw GSysError( "Error writing byte array");
		}
		toWrite		-= written;
		pData		+= written;
		
		if ( toWrite > 0 ) qDebug("writing data ,second iteration"); // TODO remove
		// wait for more
		if ( ! pDevice->waitForBytesWritten( timeout ) )
		{
			throw GTimeoutError("Timeout writing byte array data");
		}
	}
}

// ----------------------------- read() --------------------------------------
void GSerializableItem::read( QIODevice * device )
{    
        // check out for a proper pointer
    if( !device ) {
        return;
    }
    
        // create stream to read from
    QDataStream in( device );
    in.setVersion( QDATASTREAM_VERSION );
    
    read( in );
}
// ---------------------------------------------------------------------------
    
    
// --------------------------- operator << -----------------------------------
QDataStream & operator << ( QDataStream & out, const GSerializableItem & item )
{
        // write item to the stream
    item.write( out );
    
        // return the stream
    return out;
}
// ---------------------------------------------------------------------------


// --------------------------- operator >> -----------------------------------
QDataStream & operator >> ( QDataStream & in, GSerializableItem & item )
{
        // read item from the stream
    item.read( in );
    
        // return the stream
    return in;
}
// ---------------------------------------------------------------------------




// ===========================================================================
//                                GHeader
// ===========================================================================


// ------------------------------ GHeader() ----------------------------------
GHeader::GHeader( const GHeader & header )
    : QObject( header.parent() ), GSerializableItem()
{
        // init type
    setType( header.type() );
}
// ---------------------------------------------------------------------------


// ------------------------- operator = --------------------------------------
GHeader & GHeader::operator = ( GHeader & header )
{
    _type = header.type();
    
    return * this;
}
// ---------------------------------------------------------------------------

    
// ---------------------------- write() --------------------------------------
void GHeader::write( QDataStream & out ) const
{   
        // write magic to the stream 
    out << DEFAULT_MAGIC;
    
        // create buffer and the stream for header data 
    QByteArray buffer;
    QDataStream bufferOut(&buffer, QIODevice::WriteOnly);
    bufferOut.setVersion(QDATASTREAM_VERSION);
    
        // write header data to the buffer 
    writeData( bufferOut );
    
        // write buffer to the stream 
    out << buffer;
}
// ---------------------------------------------------------------------------
    
    
// ---------------------------- read() ---------------------------------------
void GHeader::read( QDataStream & in )
{
    // synchronieze
    if ( in.device()->bytesAvailable() < (int)sizeof( quint32 ) ) {
        return;
    }
        // read magic 
    quint32 magic = 0;
    in >> magic;
    
        // confirm magic 
    if ( magic == DEFAULT_MAGIC ) {
            // create buffer for header data and read the data 
        QByteArray buffer;
        //in >> buffer;
        readBuffer( in.device(), buffer, -1 );
    
            // create data stream on the buffer
        QDataStream bufferIn(&buffer, QIODevice::ReadOnly);
        bufferIn.setVersion(QDATASTREAM_VERSION);
    
            // process with data in buffer 
        readData( bufferIn );
    }
    else
    {
    	throw GValueError("invalid header magic");
    }
}
// ---------------------------------------------------------------------------
// ---------------------------- readFromSocket() ---------------------------------------
/// Reads packet form asynchronous device, as QTcpSocket. The read itself is synchronous
///@param pDevice	asynchronous device (ie QTcpSocket or QProcess)
///@param timeout	read timeout [ms]
///throws timeout exception on timeout
void GHeader::readFromSocket( QIODevice* pDevice, int timeout  )
{
	Q_ASSERT( pDevice );
	
	// wait for magic
	while( (size_t)pDevice->bytesAvailable() < sizeof( quint32 ) )
	{
		if ( ! pDevice->waitForReadyRead( timeout ) )
		{
			throw GTimeoutError("Timeout waiting for header");
		}
	}
        // read magic 
    quint32 magic = 0;
    QDataStream( pDevice ) >> magic;
    
        // confirm magic 
    if ( magic == DEFAULT_MAGIC ) {
            // create buffer for header data and read the data 
        QByteArray buffer;
        readBuffer( pDevice, buffer, timeout );
    
            // create data stream on the buffer
        QDataStream bufferIn(&buffer, QIODevice::ReadOnly);
        bufferIn.setVersion(QDATASTREAM_VERSION);
    
            // process with data in buffer 
        readData( bufferIn );
    }
    else
    {
    	throw GValueError("invalid header magic");
    }
    
}
// ---------------------------- readFromSocket() ---------------------------------------
/// Writes packet to asynchronous device, as QTcpSocket. The write itself is synchronous
///@param pDevice	asynchronous device (ie QTcpSocket or QProcess)
///@param timeout	write timeout [ms]
///throws timeout exception on timeout
void GHeader::writeToSocket( QIODevice* pDevice, int timeout  ) const
{
	Q_ASSERT( pDevice );
	
	// send magic
    QDataStream( pDevice ) << DEFAULT_MAGIC;
    // make sure it was send
    if ( ! pDevice->waitForBytesWritten( timeout ) )
    {
    	throw GTimeoutError("timeout sending header magic");
    }
    
    // create buffer and the stream for header data 
    QByteArray buffer;
    QDataStream out(&buffer, QIODevice::WriteOnly);
    out.setVersion(QDATASTREAM_VERSION);
    
        // write header data to the buffer 
    writeData( out );
    
        // write buffer to the stream 
    writeBuffer( pDevice, buffer, timeout );
}


// --------------------------- writeData() -----------------------------------
void GHeader::writeData( QDataStream & out ) const
{
        // write header to stream 
    out << _type;
}
// ---------------------------------------------------------------------------


// ---------------------------- readData() -----------------------------------
void GHeader::readData( QDataStream & in )
{    
        // extract data from stream
    in >> _type;
}
// ---------------------------------------------------------------------------
    
    
// -------------------------------- info() -----------------------------------
QString GHeader::info() const
{
        // write headers info to the string
    QString info;
    info = QString( "GHeader::type(%1)" )
            .arg( type() );
    
        // return info
    return info;
}
// ---------------------------------------------------------------------------




// ===========================================================================
//                                 GPacket
// ===========================================================================


// ---------------------------- write() --------------------------------------
void GPacket::write( QDataStream & out ) const
{   
        // create buffer and stream on the buffer for packets data 
	QByteArray buffer;
    QDataStream bufferOut( &buffer, QIODevice::WriteOnly );
    bufferOut.setVersion( QDATASTREAM_VERSION );
    
        // write all fields to the buffer
    writeData( bufferOut );
        // write buffer to the stream  
    out << buffer;
}
// ---------------------------------------------------------------------------

    
// ---------------------------- read() ---------------------------------------
void GPacket::read( QDataStream & in )
{
        // read data into buffer 
    QByteArray buffer;
    in >> buffer;
    
        // create QDataStream on the buffer and readData 
	QDataStream bufferIn( &buffer, QIODevice::ReadOnly );
    bufferIn.setVersion( QDATASTREAM_VERSION );
    readData( bufferIn );
}

// ---------------------------- readFromSocket() ---------------------------------------
/// Reads packet form asynchronous device, as QTcpSocket. The read itself is synchronous
///@param pDevice	asynchronous device (ie QTcpSocket or QProcess)
///@param timeout	read timeout [ms]
///throw UQTimeoutError on timeout
void GPacket::readFromSocket( QIODevice* pDevice, int timeout )
{
	Q_ASSERT( pDevice );
        // read data into buffer 
    QByteArray buffer;
	readBuffer( pDevice, buffer, timeout );
    
        // create QDataStream on the buffer and readData 
	QDataStream bufferIn( &buffer, QIODevice::ReadOnly );
    bufferIn.setVersion( QDATASTREAM_VERSION );
    readData( bufferIn );
}
// ---------------------------- writeToSocket() ---------------------------------------
/// Writes packet to asynchronous device, as QTcpSocket. The write itself is synchronous
///@param pDevice	asynchronous device (ie QTcpSocket or QProcess)
///@param timeout	write timeout [ms]
///throw UQTimeoutError on timeout
void GPacket::writeToSocket( QIODevice* pDevice, int timeout ) const
{
	Q_ASSERT( pDevice );
    
    QByteArray buffer;
        // create QDataStream on the buffer and readData 
	QDataStream in( &buffer, QIODevice::WriteOnly );
    in.setVersion( QDATASTREAM_VERSION );
    writeData( in );
	
	writeBuffer( pDevice, buffer, timeout );
    
}
// ---------------------------------------------------------------------------



